package brickdestroyer.simulation;

import java.awt.Graphics;
import java.util.List;
import java.util.Random;
import static brickdestroyer.simulation.Brick.*;
import static brickdestroyer.simulation.BrickType.*;
import static brickdestroyer.simulation.Simulation.*;

/**
 * This class manages all the bricks in the game.
 * <p>It is responsible for changing levels, checking
 * if the game hasn't ended in the player's favour and etc.
 * 
 * @author Momchil Atanasov
 */
public final class BrickManager {

    /*
     * Horizontal number of bricks in the game.
     */
    private static final int BRICK_COUNT_X = 8;

    /*
     * Vertical number of bricks in the game.
     */
    private static final int BRICK_COUNT_Y = 4;
    
    /* Custom levels */
    private static final int LEVEL_01 = 1;
    private static final BrickType[][] LEVEL_01_MATRIX =
    {{ID_NONE,  ID_GREEN, ID_NONE,  ID_NONE, ID_RED,  ID_NONE, ID_GREY, ID_GREY},
     {ID_GREEN, ID_NONE,  ID_GREEN, ID_RED,  ID_NONE, ID_RED,  ID_GREY, ID_GREY},
     {ID_GREEN, ID_NONE,  ID_GREEN, ID_RED,  ID_NONE, ID_RED,  ID_GREY, ID_NONE},
     {ID_NONE,  ID_GREEN, ID_NONE,  ID_NONE, ID_RED,  ID_NONE, ID_GREY, ID_NONE}};

    private static final int LEVEL_03 = 3;
    private static final BrickType[][] LEVEL_03_MATRIX =
    {{ID_GREY,   ID_GREY, ID_GREY,  ID_GREY, ID_GREY,     ID_GREY, ID_GREY,    ID_GREY},
     {ID_RED,    ID_GREY, ID_RED,   ID_GREY, ID_RED,      ID_GREY, ID_RED,     ID_GREY},
     {ID_BOUNCE, ID_STAR, ID_BALL,  ID_STAR, ID_FRICTION, ID_STAR, ID_GRAVITY, ID_STAR},
     {ID_GREEN,  ID_GREY, ID_GREEN, ID_BALL, ID_GREEN,    ID_GREY, ID_GREEN,   ID_GREY}};

    private static final int LEVEL_05 = 5;
    private static final BrickType[][] LEVEL_05_MATRIX =
    {{ID_GREY, ID_GREY, ID_GREY,    ID_GREY,  ID_GREY, ID_GREY,    ID_GREY, ID_GREY},
     {ID_GREY, ID_GREY, ID_NONE,    ID_NONE,  ID_NONE, ID_NONE,    ID_GREY, ID_GREY},
     {ID_GREY, ID_NONE, ID_GRAVITY, ID_BALL,  ID_BALL, ID_GRAVITY, ID_NONE, ID_GREY},
     {ID_GREY, ID_NONE, ID_BALL,    ID_GREEN, ID_GREEN, ID_BALL,    ID_NONE, ID_GREY}};

    private static final int LEVEL_07 = 7;
    private static final BrickType[][] LEVEL_07_MATRIX =
    {{ID_GREY,   ID_NONE,     ID_GREY,   ID_NONE,  ID_NONE,  ID_GREY,     ID_NONE,    ID_GREY},
     {ID_RED,    ID_GREY,     ID_GREEN,  ID_STAR,  ID_STAR,  ID_GREEN,    ID_GREY,    ID_RED},
     {ID_BOUNCE, ID_FRICTION, ID_BALL,   ID_RED,   ID_RED,   ID_BALL,     ID_GRAVITY, ID_FRICTION},
     {ID_GREEN,  ID_RED,      ID_BOUNCE, ID_GREEN, ID_GREEN, ID_FRICTION, ID_RED,     ID_GREEN}};
   

    /*
     * Current game level
     */
    private int level = 0;

    private final Simulation simulation;

    /*
     * Matrix holding all the bricks.
     */
    private final Brick[][] bricks = new Brick[BRICK_COUNT_Y][BRICK_COUNT_X];
    
    /**
     * Creates a brick manager and initializes the game
     * to level 01.
     */
    public BrickManager(Simulation simulation) {
        this.simulation = simulation;

        initializeBricks();
        setLevel(1);
    }

    private final void initializeBricks() {
        final float BRICK_HALF_WIDTH = DEFAULT_BRICK_WIDTH / 2.0f;
        final float BRICK_HALF_HEIGHT = DEFAULT_BRICK_HEIGHT / 2.0f;
        float brickY = PLAYABLE_AREA_TOP + BRICK_HALF_HEIGHT;
        for (int y = 0; y < BRICK_COUNT_Y; ++y) {
            float brickX = PLAYABLE_AREA_LEFT + BRICK_HALF_WIDTH;
            for (int x = 0; x < BRICK_COUNT_X; ++x) {
                bricks[y][x] = new Brick(simulation, brickX, brickY);
                brickX += DEFAULT_BRICK_WIDTH;
            }
            brickY += DEFAULT_BRICK_HEIGHT;
        }
    }

    /**
     * Returns the brick at the given matrix position.
     * @param matrixX X matrix index.
     * @param matrixY Y matrix index.
     * @return the required brick
     */
    public final Brick getBrick(int matrixX, int matrixY) {
        boolean inMatrix = (matrixX >= 0) && (matrixY >= 0) &&
                           (matrixX < BRICK_COUNT_X) && (matrixY < BRICK_COUNT_Y);
        return inMatrix ? bricks[matrixY][matrixX] : null;
    }

    /**
     * Sets the game to a new level
     * @param level new game level
     */
    public void setLevel(int level) {
        this.level = level;

        BrickType[][] levelMatrix;
        switch(level) {
            case LEVEL_01: {
                levelMatrix = LEVEL_01_MATRIX;
                break;
            }
            case LEVEL_03: {
                levelMatrix = LEVEL_03_MATRIX;
                break;
            }
            case LEVEL_05: {
                levelMatrix = LEVEL_05_MATRIX;
                break;
            }
            case LEVEL_07: {
                levelMatrix = LEVEL_07_MATRIX;
                break;
            }
            default: {
                levelMatrix = createRandomLevelMatrix();
                break;
            }
        } 
        
        for (int y = 0; y < BRICK_COUNT_Y; ++y) {
            for (int x = 0; x < BRICK_COUNT_X; ++x) {
                bricks[y][x].setType(levelMatrix[y][x]);
            }
        }
    }

    /*
     * Creates a random level matrix
     */
    private static BrickType[][] createRandomLevelMatrix() {
        BrickType[][] result = new BrickType[BRICK_COUNT_Y][BRICK_COUNT_X];

        Random random = new Random();
        List<BrickType> brickTypes = BrickType.listBrickTypes();
        for (int y = 0; y < BRICK_COUNT_Y; y++) {
            for (int x = 0; x < BRICK_COUNT_X; x++) {
                int brickTypeIndex = random.nextInt(brickTypes.size());
                BrickType brickType = brickTypes.get(brickTypeIndex);
                if (brickType.isDestroyable()) {
                    result[y][x] = brickType;
                } else {
                    result[y][x] = BrickType.ID_GREEN;
                }
            }
        }

        return result;
    }

    /**
     * This method returns the current level of the game.
     * @return the game level
     */
    public int getLevel() {
        return level;
    }

    /**
     * This method checks if the game has ended in favour of
     * the player.
     * <p>This method returns <code>true</code> if all destryable
     * bricks have been distroyed.
     * @return <code>true</code> if the level has ended,
     * <code>false</code> otherwise.
     */
    public final boolean levelEnded() {
        for (int y = 0; y < BRICK_COUNT_Y; y++) {
            for (int x = 0; x < BRICK_COUNT_X; x++) {
                Brick brick = bricks[y][x];
                if (brick.needsToBeDestroyed()) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Call this method to update all bricks.
     */
    public void update() {
        /*
         * Currently this method has not been implemented.
         * The idea is that in future versions bricks could
         * have animations that would be update from here
         * or bricks could have timing effects.
         */
    }    

    /**
     * This method destroys all surrounding bricks of the one
     * passed as parameter.
     * @param brick brick who's neighbours will be destoryed.
     */
    public void destroySurroundingBricks(Brick brick) {
        final int deltaCount = 4;
        final int deltaX[] = {- 1, 1,   0, 0};
        final int deltaY[] = {  0, 0, - 1, 1};
        
        int brickX = positionToBrickIndexX(brick.x);
        int brickY = positionToBrickIndexY(brick.y);
               
        for (int i = 0; i < deltaCount; ++i) {
            Brick destroyBrick = getBrick(brickX + deltaX[i], brickY + deltaY[i]);
            if (destroyBrick != null) {
                destroyBrick.destroy();
            }
        }
    }

    /**
     * Returns the brick X index that corresponds to the given
     * coordinate on the game screen.
     * @param x X screen coordinate
     * @return X matrix index
     */
    public final int positionToBrickIndexX(float x) {
        return (int)((x - PLAYABLE_AREA_LEFT) / DEFAULT_BRICK_WIDTH);
    }

    /**
     * Returns the brick Y index that corresponds to the given
     * coordinate on the game screen.
     * @param y Y screen coordinate
     * @return Y matrix index
     */
    public final int positionToBrickIndexY(float y) {
        return (int)((y - PLAYABLE_AREA_TOP) / DEFAULT_BRICK_HEIGHT);
    }

    /**
     * This method displays all bricks on the screen.
     * @param g interface to the graphics handle.
     */
    public void display(Graphics g) {
        for (int y = 0; y < BRICK_COUNT_Y; ++y) {
            for (int x = 0; x < BRICK_COUNT_X; ++x) {
                bricks[y][x].display(g);
            }
        }
    }
}
